/* 
 * File:   LambertianShader.cpp
 * Author: brady
 * 
 * Created on February 8, 2011, 11:10 AM
 */

#include "LambertianShader.h"
#include <stdlib.h>

LambertianShader::LambertianShader() {
    LambertianShader(0.5, 0.5, 0.5);
}

LambertianShader::LambertianShader(const LambertianShader& orig)
{
    this->AmbientCoefficient = orig.AmbientCoefficient;
    this->Color = orig.Color;
    this->texture = orig.texture;
    this->recursionDepth = orig.recursionDepth;
}
LambertianShader::LambertianShader(double r, double g, double b)
{
    SetColor(r, g, b);
    SetAmbientCoeff(0.05);
    SetRecursionDepth(0);
}

LambertianShader::~LambertianShader() {
}

void LambertianShader::SetColor(double r, double g, double b)
{
    Color = Vector3D(r, g, b);
}

void LambertianShader::SetColor(Vector3D color)
{
    Color = color;
}

Vector3D LambertianShader::ComputeLighting(Ray& hitRay, Vector3D &normal, Scene* scene)
{
    Vector3D point(hitRay.GetOrigin());

    Vector3D light = Vector3D(0.0, 0.0, 0.0);
    for(int i = 0; i < scene->lights.size(); i++){
        Light* curLight = scene->lights[i];
        Vector3D lightOrigin;
        if(scene->SoftShading)
        {
            //this could take a ray as well and GetRandomPoint can extract the info.
            lightOrigin = curLight->GetRandomPoint(hitRay, scene->SamplesPerPixel);
        }
        else lightOrigin = curLight->GetOrigin();
        Vector3D lightColor = curLight->GetColor();
        
        //Set up my Ray towards the light;
        Vector3D lightDir = lightOrigin - point;
        lightDir.normalize();
        Ray lightRay = Ray(point,lightDir);
        lightRay.sNum = hitRay.sNum;
        double lightDistance = Magnitude(lightOrigin - point);
        
        //Test for shadows
        //if lightray hits an object, shadow coeff = 0.0
        //otherwise compute the shadow coefficient to unobstructed light
        double shadowCoeff;
        HitStruct hs;
        bool hit = scene->BVHRoot->TestIntersect(lightRay, scene->Tmin, scene->Tmax, hs);
        if (hit && hs.tVal < lightDistance){
            shadowCoeff = 0.0;
        }
        else {
            lightDir = curLight->GetOrigin();
            lightDir.normalize();
            shadowCoeff = GetShadowCoeffecient(normal, lightDir);
        }
        //add this light value to the total light hitting the object
        light += lightColor * shadowCoeff;
    }
    return light;
}

double LambertianShader::GetShadowCoeffecient(Vector3D &normal, Vector3D &toLight)
{
    double shadeCoeff =  normal.dot(toLight);
    if (shadeCoeff < 0.0) return 0.0;
    else return shadeCoeff;
}



Vector3D LambertianShader::Shade(Ray &viewingRay, HitStruct& hitStruct, Scene* scene)
{
    Vector3D totalLight(0.0, 0.0, 0.0);
    Vector3D viewDir = viewingRay.GetDirection();
    viewDir *= -1.0;
    Ray hitRay(viewingRay.Follow(hitStruct.tVal), viewDir);
    hitRay.sNum = viewingRay.sNum;
    hitRay.RefractiveIndex = viewingRay.RefractiveIndex;
    hitRay.SetRecursionDepth(viewingRay.GetRecursionDepth());
    Vector3D surfaceNormal = hitStruct.normal;
    //calculate displacement
    Vector3D displacement = this->texture->GetNormalOffsetAt(hitRay.GetOrigin());
    surfaceNormal += displacement;
    surfaceNormal.normalize();

    totalLight = ComputeLighting(hitRay, surfaceNormal, scene);
    Vector3D pointColor = this->texture->GetColorAt(hitRay.GetOrigin());
    Vector3D finalColor =
            Mix(pointColor, totalLight) * (1-AmbientCoefficient) + (pointColor * AmbientCoefficient);
    finalColor = Cap(finalColor, 1.0);
    return finalColor;
}